UNIX V6 源码分析笔记
Table of Contents
UNIX V6 源码可以在这里获得:https://github.com/dspinellis/unix-history-repo/tree/Research-V6-Snapshot-Development
1. UNIX V6 核心功能
- 块设备和字符设备
- 系统调用
- 进程管理:进程调度、进程创建
- 文件系统
- 交换空间
- 系统初始化:启动、进程初始化
- 多用户
- 中断处理
- shell
- 管道
2. PDP-11/40 基本知识
UNIBUS 总线将各个部件连接在一起,如下结构:
<====================UNIBUS====================> | | | | +-----+ +-----+ +------+ +-----+ | CPU | | 内存 | | I/O | | I/O | .... +-----+ +-----+ +------+ +-----+
这样每个 I/O 的寄存器就可以映射到内存地址,通过访问内存来读写 I/O。比如可以直接读写内存地址来操作 CPU 的寄存器。
UNIBUS 的地址总线宽度为 18bit。内存的最高位的 8KB 空间用来映射 I/O 寄存器。
2.1. 寄存器
7 个通用寄存器,r0 ~ r7。其中 r6 有两个,分别在内核空间和用户空间,在切换模式时,r6 也会跟真切换。通用寄存器大小 8KB。
各寄存器含义如下:
寄存器 | 说明 |
---|---|
r0,r1 | 存储运和函数的返回值 |
r2,r3,r4 | 本地处理(就是程序自由使用) |
r5 | 帧指针 |
r6 | SP,栈指针,硬件层面上,用户模式和内核模式各有一个 |
r7 | PC,程序计数器 |
PSW 寄存器:处理器状态字寄存器,宽度为 16 位。
比特位说明:
比特位 | 说明 |
---|---|
0 | 借位位,借位时值为 1 |
1 | 溢出位,溢出时值为 1 |
2 | 零位,指令执行结果为 0 时,这里置为 1 |
3 | 负位,指令执行结果为负时,这里置为 1 |
4 | 陷入(trap) |
5~7 | 处理器优先级,取值范围:0~7 |
8~11 | 无用途 |
12~13 | 处理器先前模式 |
14~15 | 处理器当前模式 |
其中 0~3 位为条件码,12~15 位为模式位,00 表示内核模式,11 表示用户模式。
PSW 寄存器映射到内存地址 0177776,定义在 V6 源码中:
#define PS 0177776
3. 进程
所有进程都保存在全局数组 proc 中,每个元素都是一个 proc 结构体类型。定义如下:
struct proc { char p_stat; char p_flag; char p_pri; /* priority, negative is high */ char p_sig; /* signal number sent to this process */ char p_uid; /* user id, used to direct tty signals */ char p_time; /* resident time for scheduling */ char p_cpu; /* cpu usage for scheduling */ char p_nice; /* nice for scheduling */ int p_ttyp; /* controlling tty */ int p_pid; /* unique process id */ int p_ppid; /* process id of parent */ int p_addr; /* address of swappable image */ int p_size; /* size of swappable image (*64 bytes) */ int p_wchan; /* event process is awaiting */ int *p_textp; /* pointer to text structure */ } proc[NPROC];
proc 结构体保存了进程信息,比如 PID、UID 等等。
其中 NPROC 定义了系统最多的进程数:
#define NPROC 50 /* max number of processes */
另一个结构体是 user,用来保存进程资源占用的信息:
struct user { int u_rsav[2]; /* 进程切换时保存 r5 和 r6 寄存器的值 */ int u_fsav[25]; /* save fp registers */ /* rsav and fsav must be first * in structure * PDP-11/40不用 */ char u_segflg; /* flag for IO; user or kernel space */ char u_error; /* 出错时保存状态码 */ char u_uid; /* 实效 UID */ char u_gid; /* 实效 GID */ char u_ruid; /* 实际 UID */ char u_rgid; /* 实际 GID */ int u_procp; /* 指向 proc[] 元素 */ char *u_base; /* base address for IO */ char *u_count; /* bytes remaining for IO */ char *u_offset[2]; /* offset in file for IO */ int *u_cdir; /* pointer to inode of current directory */ char u_dbuf[DIRSIZ]; /* current pathname component */ char *u_dirp; /* current pointer to inode */ struct { /* 当前目录 */ int u_ino; char u_name[DIRSIZ]; /* 目录名 */ } u_dent; int *u_pdir; /* inode of parent directory of dirp */ int u_uisa[16]; /* prototype of segmentation addresses */ int u_uisd[16]; /* prototype of segmentation descriptors */ int u_ofile[NOFILE]; /* pointers to file structures of open files */ int u_arg[5]; /* arguments to current system call */ int u_tsize; /* 代码段大小 (*64) */ int u_dsize; /* 数据段大小 (*64) */ int u_ssize; /* 栈大小 (*64) */ int u_sep; /* flag for I and D separation */ int u_qsav[2]; /* label variable for quits and interrupts */ int u_ssav[2]; /* label variable for swapping */ int u_signal[NSIG]; /* disposition of signals */ int u_utime; /* 用户模式下 CPU 滴答数 */ int u_stime; /* 内核模式下 CPU 滴答数 */ int u_cutime[2]; /* sum of childs' utimes */ int u_cstime[2]; /* sum of childs' stimes */ int *u_ar0; /* address of users saved R0 */ int u_prof[4]; /* profile arguments */ char u_intflg; /* catch intr from sys */ /* kernel stack per user * extends from u + USIZE*64 * backward not to reach here */ } u;
user 结构体由全局变量 u 表示。
3.1. 进程状态
V6 的进程大体上有两个状态:可执行和休眠。
状态定义如下:
#define SSLEEP 1 /* 高优先级休眠 */ #define SWAIT 2 /* 低优先级休眠 */ #define SRUN 3 /* 可执行状态 */ #define SIDL 4 /* 进程生成中 */ #define SZOMB 5 /* 僵尸 */ #define SSTOP 6 /* 能够被跟踪(trace) */
proc 结构体的 p_flag 定义了进程的标志常量:
#define SLOAD 01 /* 处于内存中 */ #define SSYS 02 /* 系统进程,不会被 swap 出去,UNIX V6 只有 proc[0] */ #define SLOCK 04 /* 调度锁,锁住后不让进程被 swap 出去 */ #define SSWAP 010 /* 进程已经被 swap 出去 */ #define STRC 020 /* 进程处于被跟踪(trace)状态 */ #define SWTED 040 /* 跟踪时使用的另一个状态 */
3.2. 内存分配
进程分代码段和数据段。
代码段由数组 text[] 管理,进程代码段的长度由 user.u_tsize 表示。
由于多个相同代码的进程可以共享一个代码段,所以 text[] 是全局的。text 结构体定义如下:
/* V6 实现了内存共享,因为代码段的内容是不可变的, * 所以 text 是全局变量,如果存在多个执行文件副本,在内存 * 中是共享一份 */ struct text { int x_daddr; /* 磁盘中的地址 disk address of segment */ int x_caddr; /* 读入内存后的物理地址 core address, if loaded */ int x_size; /* 代码段长度,以 64 字节为分块单位 size (*64) */ int *x_iptr; /* inode中对应文件的指针 inode of prototype */ char x_count; /* 所有进程对它的引用计数 reference count */ char x_ccount; /* 内存中进程的引用计数 number of loaded references */ } text[NTEXT]; /* NTEXT 为 40 */
数据段大小保存在 proc.p_size 中。段包含了 3 个区域:内核栈、用户模式的数据区域和用户模式的栈区域。proc.p_size 包含了 3 个段的大小:
- proc.p_addr:指向内核栈区域起始,即 PPDA(Per Process Data Area),范围又 USIZE 定义:
#define USIZE 16 /* size of user block (*64) */
- user.u_dsize:数据区(静态变量、堆区域等)大小
- user.u_ssize:栈大小
3.3. 虚拟地址
每个进程拥有长度 16 位(64KB)的虚拟地址空间。通过 MMU 把 16 位虚拟地址转换成 18 位物理地址。物理地址大小:256KB。使用虚拟地址的目的是为了不让程序直接访问物理内存,因为要访问的物理内存可能是其他进程在使用,使用虚拟地址就不需要程序去关注物理内存的管理,保证了独立性。
3.4. 物理地址转换
MMU 有两个寄存器,SR0 和 SR2,SR0 保存出错信息和内存有效的标志位;SR2 保存 16 位虚拟地址。MMU 通过 APR(Active Page Register)寄存器完成虚拟地址到物理地址转换;APR 由 PAR(Page Address Register)和 PDR(Page Description Register)组成。
PAR 定义如下:
0~11 | 基地址 |
PDR 定义如下:
1~2 | 页的访问控制方法。00:未分配;01:只读;11:只写 |
3 | 为 1 时,页按高地址向低地址的方向匹配 |
6 | 表示页是否更新 |
8~14 | 页的块数 |
APR 一共有 8 组(0~7),虚拟地址空间以页为单位。1 组 APR 对应 1 页,每页最多 128个块(8KB)。
虚拟地址寻址过程:
虚拟地址的高 3 位决定了页(APR),PAR 的基地址+虚拟地址 12~6bit 得到物理内存的块地址,再加上虚拟地址 5~0 位(块偏移地址),得到物理地址。
3.5. 创建进程
UNIX 进程是一个树状关系,创建一个新的进程为子进程,原进程为子进程的父进程,子进程继承了父进程的数据,比如打开的文件等等。
通过系统调用 fork 创建进程,父进程调用 wait 等待子进程执行完毕。子进程调用 exec 把程序载入内存执行。子进程结束后调用 exit。
进程保存在 proc 数组中。
全局变量 mpid 用来保存 PID:
int mpid; /* generic for unique process id's */
3.5.1. fork
fork 是一个系统调用,用来创建一个新进程,最终工作的函数是 newproc,它主要完成几个工作:
- 在 proc 数组中找出空位置
- 生成一个新的 PID
- 复制父进程的数据,并为子进程分配空间
过程:
在用户模式下调用 C 语言的 fork 函数,首先调用系统中断:
sys fork
然后调用内核里的 fork 函数:
/* * fork system call. */ fork() { register struct proc *p1, *p2; /* u 当前是父进程的数据 */ p1 = u.u_procp; /* 在 proc 中寻找空位 */ for(p2 = &proc[0]; p2 < &proc[NPROC]; p2++) if(p2->p_stat == NULL) goto found; u.u_error = EAGAIN; goto out; found: /* 注意 newproc 返回给父进程的值为 0 * 所以这里 if 判断为 false * 但是子进程返回的是 1,所以执行逻辑体中代码 */ if(newproc()) { /* 子进程的返回值设置为父进程的 PID */ u.u_ar0[R0] = p1->p_pid; u.u_cstime[0] = 0; u.u_cstime[1] = 0; u.u_stime = 0; u.u_cutime[0] = 0; u.u_cutime[1] = 0; u.u_utime = 0; return; } /* 把 p_pid 放到 r0 寄存器中,r0 存储返回值,这是给父进程的 */ u.u_ar0[R0] = p2->p_pid; out: /* r7 保存的是计数器,移动到下条指令 */ u.u_ar0[R7] =+ 2; }
fork 函数最终让子进程调用 newproc 函数完成进程创建:
newproc() { int a1, a2; struct proc *p, *up; register struct proc *rpp; register *rip, n; p = NULL; /* * First, just locate a slot for a process * and copy the useful info from this process into it. * The panic "cannot happen" because fork has already * checked for the existence of a slot. */ retry: /* 生成新进程的 PID */ mpid++; /* 如果 PID 的值超出整型范围,就从 0 开始 */ if(mpid < 0) { mpid = 0; /* 因为 PID 0 是系统进程,所以不能为 0 */ goto retry; } /* 从 proc 数组中找出未分配的进程信息 */ for(rpp = &proc[0]; rpp < &proc[NPROC]; rpp++) { /* proc.p_stat 为 NULL,表示可用 */ if(rpp->p_stat == NULL && p==NULL) p = rpp; /* 说明 PID 被占用了 */ if (rpp->p_pid==mpid) goto retry; } if ((rpp = p)==NULL) panic("no procs"); /* * make proc entry for new proc */ /* 把父进程的信息拷贝给子进程 */ rip = u.u_procp; up = rip; rpp->p_stat = SRUN; /* 标记成可运行,进程调度器就可以让它创建 之后执行了 */ rpp->p_flag = SLOAD; /* 表示数据处于内存中,而不是 swap 出去了 */ rpp->p_uid = rip->p_uid; rpp->p_ttyp = rip->p_ttyp; rpp->p_nice = rip->p_nice; rpp->p_textp = rip->p_textp; /* 拷贝代码区 */ rpp->p_pid = mpid; /* 上面做了很多事就是生成 PID * 生成 PID 其实可以独立成一个函数? */ rpp->p_ppid = rip->p_pid; /* 父进程 PID 就是当前进程 */ rpp->p_time = 0; /* 还没有开始运行,所以 CPU 的滴答数为 0 */ /* * make duplicate entries * where needed */ /* 复制文件句柄 */ for(rip = &u.u_ofile[0]; rip < &u.u_ofile[NOFILE];) if((rpp = *rip++) != NULL) /* 由于子进程继承父进程打开的文件句柄 * 所以计数器要加 1 */ rpp->f_count++; if((rpp=up->p_textp) != NULL) { /* 因为和父进程共享,所以计数器都加 1 */ rpp->x_count++; rpp->x_ccount++; } u.u_cdir->i_count++; /* * Partially simulate the environment * of the new process so that when it is actually * created (by copying) it will look right. */ savu(u.u_rsav); rpp = p; /* 这里是暂时把父进程的 u_procp 切换到子进程上 * 让子进程的数据虽是父进程的拷贝,却又有属于自己的 */ u.u_procp = rpp; rip = up; n = rip->p_size; /* 这里获得父进程的内存大小,后面分配需要 */ a1 = rip->p_addr; rpp->p_size = n; /* 为子进程分配和父进程相同大小的空间 */ a2 = malloc(coremap, n); /* * If there is not enough core for the * new process, swap out the current process to generate the * copy. */ /* 处理空间不够的情况 */ if(a2 == NULL) { rip->p_stat = SIDL; /* 设置进程状态为还在生成中 * 防止被执行 */ rpp->p_addr = a1; savu(u.u_ssav); /* 把父进程的数据换出到交换空间中 */ xswap(rpp, 0, 0); rpp->p_flag =| SSWAP; rip->p_stat = SRUN; } else { /* * There is core, so just copy. */ rpp->p_addr = a2; while(n--) copyseg(a1++, a2++); } u.u_procp = rip; return(0); }
3.6. 进程调度
调度算法
swtch 函数实现进程的调度算法:
- 选出标记为 SRUN 状态、p_flag 为 SLOAD 的进程
- 拥有最高执行优先级,即 p_pri 值最小的进程
关键算法的代码如下:
/* 遍历 proc,选出优先级最高的 */ i = NPROC; do { rp++; if(rp >= &proc[NPROC]) rp = &proc[0]; /* 必须是可执行的进程 */ if(rp->p_stat==SRUN && (rp->p_flag&SLOAD)!=0) { /* 选出最小 p_pri 的 */ if(rp->p_pri < n) { p = rp; n = rp->p_pri; } } } while(--i);
休眠和唤醒:
- 让当前进程进入休眠状态:sleep
- 唤醒进程:wakeup
3.7. 交换处理
交换处理用来处理使用内存资源时,内存不够的情况。
sched 函数用来交换处理对象的进程,在 main 函数创建 proc[0] 后,就开始调用,所以 proc[0] 也可以叫做调度进程。
sched 主要就是遍历 proc[0],寻找可换出/换入的进程,如果换入时调用 malloc 分配空间存在内存不足时,就再遍历 proc,找出可换出的进程并换出,然后再次换入新进程;如果都失败,就休眠,直到收到中断后,再从 proc 遍历寻找。sched 函数实现如下:
sched() { struct proc *p1; register struct proc *rp; register a, n; /* * find user to swap in * of users ready, select one out longest */ goto loop; sloop: /* 不存在换出对象,就进入休眠状态,直到被中断 */ runin++; sleep(&runin, PSWP); loop: /* 设置中断优先级为 6,防止被中断 */ spl6(); n = -1; for(rp = &proc[0]; rp < &proc[NPROC]; rp++) /* 寻找运行时间最长的进程,如果进程是运行中,并且不是 SLOAD */ if(rp->p_stat==SRUN && (rp->p_flag&SLOAD)==0 && /* rp->p_flag&SLOAD 等价于 !SLOAD */ rp->p_time > n) { p1 = rp; n = rp->p_time; } /* 没有找到就设置 runout,然后进入休眠状态 * 直到被中断后继续寻找 */ if(n == -1) { runout++; sleep(&runout, PSWP); goto loop; } /* * see if there is core for that process */ spl0(); rp = p1; a = rp->p_size; /* 如果换入的进程在代码段中不存在 * 表示代码段也需要换入 */ if((rp=rp->p_textp) != NULL) if(rp->x_ccount == 0) a =+ rp->x_size; /* 为代码段分配内存 */ if((a=malloc(coremap, a)) != NULL) goto found2; /* * none found, * look around for easy core */ /* 处理内存不足的情况 */ spl6(); /* 如果内存不够,就把不重要的进程给换出了 * 既找出:存在内存中(SLOAD)、不是系统进程(SSYS)、 * 不是被锁的进程(SLOCK)、处于睡眠状态(SWAIT)或者处于停止 * 状态(SSTOP)*/ for(rp = &proc[0]; rp < &proc[NPROC]; rp++) if((rp->p_flag&(SSYS|SLOCK|SLOAD))==SLOAD && (rp->p_stat == SWAIT || rp->p_stat==SSTOP)) /* 如果找到就跳到 found1 */ goto found1; /* * no easy core, * if this process is deserving, * look around for * oldest process in core */ /* 换出进程距离上次不能小于 3 秒,小于 3 秒表示太频繁 * 跳到 sloop 继续休眠 */ if(n < 3) goto sloop; /* 设置 n 为 -1,再来寻找运行时间过长,可换出的进程 */ n = -1; for(rp = &proc[0]; rp < &proc[NPROC]; rp++) if((rp->p_flag&(SSYS|SLOCK|SLOAD))==SLOAD && (rp->p_stat==SRUN || rp->p_stat==SSLEEP) && rp->p_time > n) { p1 = rp; n = rp->p_time; } /* 距上次换出时间小于 2 秒,又阻塞 */ if(n < 2) goto sloop; rp = p1; /* * swap user out */ found1: spl0(); /* 清除状态位(SLOAD),执行换出处理 */ rp->p_flag =& ~SLOAD; /* 第二个 2 参数为 1,表示从内存中释放进程 */ xswap(rp, 1, 0); goto loop; /* * swap user in */ found2: /* found2 是执行换入处理 */ if((rp=p1->p_textp) != NULL) { if(rp->x_ccount == 0) { if(swap(rp->x_daddr, a, rp->x_size, B_READ)) goto swaper; rp->x_caddr = a; a =+ rp->x_size; } rp->x_ccount++; } rp = p1; if(swap(rp->p_addr, a, rp->p_size, B_READ)) goto swaper; mfree(swapmap, (rp->p_size+7)/8, rp->p_addr); rp->p_addr = a; /* 设置标志位为 SLOAD */ rp->p_flag =| SLOAD; /* 设置新的运行时间 */ rp->p_time = 0; goto loop; swaper: panic("swap error"); }
数据段和代码段分别用 xswap 和 xalloc 函数用来完成交换工作。
xswap(p, ff, os) int *p; { register *rp, a; rp = p; /* 如果被换出的长度为 0,表示要换出整个进程的数据段 * 为啥不为这个参数定义个宏呢,感觉可读性不好啊 */ if(os == 0) os = rp->p_size; /* 分配交换空间 */ a = malloc(swapmap, (rp->p_size+7)/8); if(a == NULL) panic("out of swap space"); /* 递减代码段的计数器 */ xccdec(rp->p_textp); /* 设置进程为调度锁 */ rp->p_flag =| SLOCK; /* 执行换出 */ if(swap(a, rp->p_addr, os, 0)) panic("swap error"); /* 如果 ff 为 1,表示要释放内存 */ if(ff) mfree(coremap, os, rp->p_addr); /* 把数据段的地址设置成交换空间的地址 */ rp->p_addr = a; /* 清除标志位 */ rp->p_flag =& ~(SLOAD|SLOCK); /* 重新设置运行时间 */ rp->p_time = 0; /* 重置 runout */ if(runout) { runout = 0; wakeup(&runout); } } xalloc(ip) int *ip; { register struct text *xp; register *rp, ts; /* u_agr[1]为 exec 执行时设置的代码段长度 * 长度为 0 不做任何处理 */ if(u.u_arg[1] == 0) return; rp = NULL; /* 遍历 text[],如果代码段已经存在 text[] 里,x_count 递增 */ for(xp = &text[0]; xp < &text[NTEXT]; xp++) if(xp->x_iptr == NULL) { if(rp == NULL) rp = xp; } else if(xp->x_iptr == ip) { xp->x_count++; u.u_procp->p_textp = xp; goto out; } if((xp=rp) == NULL) panic("out of text"); /* 代码段初始化 */ /* 引用计数加 1 */ xp->x_count = 1; xp->x_ccount = 0; xp->x_iptr = ip; ts = ((u.u_arg[1]+63)>>6) & 01777; xp->x_size = ts; /* 为代码段分配交换空间 */ if((xp->x_daddr = malloc(swapmap, (ts+7)/8)) == NULL) panic("out of swap space"); expand(USIZE+ts); estabur(0, ts, 0, 0); u.u_count = u.u_arg[1]; /* 程序执行头 */ u.u_offset[1] = 020; u.u_base = 0; readi(ip); rp = u.u_procp; rp->p_flag =| SLOCK; /* 换出数据 */ swap(xp->x_daddr, rp->p_addr+USIZE, ts, 0); rp->p_flag =& ~SLOCK; rp->p_textp = xp; rp = ip; rp->i_flag =| ITEXT; rp->i_count++; expand(USIZE); out: /* 如果没有任何内存中进程引用代码数据,就换出 */ if(xp->x_ccount == 0) { savu(u.u_rsav); savu(u.u_ssav); xswap(u.u_procp, 1, 0); u.u_procp->p_flag =| SSWAP; swtch(); /* no return */ } xp->x_ccount++; }
4. panic
当内核无法正常工作时,就调用 panic 函数:
panic(s) char *s; { panicstr = s; update(); printf("panic: %s\n", s); for(;;) idle(); }
painc 主要是死循环中不断调用 idle。idle 函数实现如下:
.globl _idle _idle: mov PS,-(sp) spl 0 wait mov (sp)+,PS rts pc
主要设置处理器优先级为 0,表示响应所有优先级的中断,然后调用 wait 等待被中断。
5. 内存管理
内存使用情况统一用 map 结构来管理:
struct map { char *m_size; /* 未使用地址的大小 */ char *m_addr; /* 未使用空间的地址 */ };
交换空间和物理内存,都是用 map 结构来记录,分别使用了两个数组:
int coremap[CMAPSIZ]; /* space for core allocation */ int swapmap[SMAPSIZ]; /* space for swap allocation */
CMAPSIZ 和 SMAPSIZ 定义如下:
#define CMAPSIZ 100 /* size of core allocation area */ #define SMAPSIZ 100 /* size of swap allocation area */
调用 malloc 分配内存。malloc 用了 First Fit 算法,传递 coremap 或 swapmap 进去,找到最先匹配到合适的空间,然后分配空间,并调整大小:
malloc(mp, size) struct map *mp; { register int a; register struct map *bp; for (bp = mp; bp->m_size; bp++) { /* 找出最先匹配大小的空间 */ if (bp->m_size >= size) { a = bp->m_addr; /* 调整未分配空间的位置 */ bp->m_addr =+ size; /* 调整大小 */ if ((bp->m_size =- size) == 0) /* 如果分配的空间刚好是需要的空间 * 就左移元素*/ do { bp++; (bp-1)->m_addr = bp->m_addr; } while ((bp-1)->m_size = bp->m_size); return(a); } } return(0); }
这里说明 swap 和内存空间是用同一种算法来管理的。
释放空间调用的 mfree 函数:
/* * Free the previously allocated space aa * of size units into the specified map. * Sort aa into map and combine on * one or both ends if possible. * 参数 aa 是要释放的地址 */ mfree(mp, size, aa) struct map *mp; { register struct map *bp; register int t; register int a; a = aa; /* 遍历 coremap 或 swapmap,一直让 bp 指向要释放的地址 */ for (bp = mp; bp->m_addr<=a && bp->m_size!=0; bp++); /* 移动位置 */ if (bp>mp && (bp-1)->m_addr+(bp-1)->m_size == a) { /* 调整大小 */ (bp-1)->m_size =+ size; if (a+size == bp->m_addr) { (bp-1)->m_size =+ bp->m_size; while (bp->m_size) { bp++; (bp-1)->m_addr = bp->m_addr; (bp-1)->m_size = bp->m_size; } } } else { if (a+size == bp->m_addr && bp->m_size) { bp->m_addr =- size; bp->m_size =+ size; } else if (size) do { t = bp->m_addr; bp->m_addr = a; a = t; t = bp->m_size; bp->m_size = size; bp++; } while (size = t); } }
6. 中断处理
周边硬件设备主动发出中断信号,然后进程被暂停,调用中断处理函数。如果没有中断处理,进程就必须不断轮询去检查设备情况。
V6 包含以下中断:
- 块设备处理
- 终端输入
- 时钟中断
陷入:来自 CPU 内部发出的信号,当出现如除以 0 错误的时候,CPU 会发出陷入事件并调用相应的处理函数(这样就不用内核去主动检测了)。
6.1. 处理器优先级和中断优先级
PSW 中处理器优先级大于等于当前的中断优先级,则不处理,直到优先级下降。
处理器优先级为 0~7,如下:
.globl _spl0, _spl1, _spl4, _spl5, _spl6, _spl7 _spl0: spl 0 rts pc _spl1: spl 1 rts pc _spl4: spl 4 rts pc _spl5: spl 5 rts pc _spl6: spl 6 rts pc _spl7: spl HIGH / HIGH的值实质上为 6 rts pc
7. 管道
管道的优点是低投入,高产出,只用分配固定的大小即可实现进程之间的通信,比起用中间的文件方式会更加省硬盘资源,比如:
a1 > tmp a2 < tmp
这样会分配 a1 产出数据大小的空间。相比之下管道效率更好,占用资源更少。